/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;

namespace CsJni
{
    [JavaToNativeCounterpart("csjni.NativeMethodRegistrar")]
    internal sealed class JavaNativeMethodRegistrar
    {
        private Dictionary<string, Type> mCounterpartMap = new Dictionary<string, Type>();

        internal JavaNativeMethodRegistrar(JavaVMConfiguration configuration, JniEnvironment environment) 
        {
            LoadCounterpartAssemblies(configuration);

            RegisterCounterpart(environment, this.GetType());
        }

        internal static JniClass GetJavaPeer(JniEnvironment environment, Type counterpartType)
        {
            string peerClassName = FindJavaPeerClassName(counterpartType);
            JniLocalFrame frame = environment.CreateLocalFrame(1);
            try
            {
                JniClass peer = JniClass.FindClass(environment, JniClass.ToJniClassName(peerClassName));
                return frame.Pop(peer) as JniClass;
            }
            finally
            {
                frame.SafePop();
            }
        }

        internal static JniClass RegisterCounterpartAndReturnPeer(JniEnvironment environment, Type counterpartType)
        {
            JniLocalFrame frame = environment.CreateLocalFrame(1);
            try
            {
                JniClass peer = GetJavaPeer(environment, counterpartType);
                RegisterCounterpart(environment, peer, counterpartType);
                return frame.Pop(peer) as JniClass;
            }
            finally
            {
                frame.SafePop();
            }
        }

        internal static void RegisterCounterpart(JniEnvironment environment, Type counterpartType)
        {
            JniLocalFrame frame = environment.CreateLocalFrame(1);
            try
            {
                JniClass peer = GetJavaPeer(environment, counterpartType);
                RegisterCounterpart(environment, peer, counterpartType);
            }
            finally
            {
                frame.SafePop();
            }
        }

        internal static void RegisterCounterpart(JniEnvironment environment, JniClass peer, Type counterpartType)
        {
            JniNativeMethodHolder nmHolder = new JniNativeMethodHolder();
            nmHolder.PopulateFromType(counterpartType);
            if (nmHolder.Count > 0)
            {
                peer.RegisterNatives(environment, nmHolder);
            }
        }

        internal static string FindJavaPeerClassName(Type possibleCounterpartType)
        {
            object[] atts = possibleCounterpartType.GetCustomAttributes(typeof(JavaToNativeCounterpartAttribute), true);
            if (atts.Length == 1)
            {
                JavaToNativeCounterpartAttribute att = (JavaToNativeCounterpartAttribute)atts[0];
                return att.JavaClassName;
            }
            return null;
        }

        private void LoadCounterpartAssemblies(JavaVMConfiguration configuration)
        {
            mCounterpartMap.Clear();

            List<string> paths = configuration.CounterpartPaths;
            foreach (string path in paths)
            {
                string fullPath = path;
                if (!fullPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
                {
                    fullPath += ".dll";
                }
                Assembly asm = Assembly.LoadFile(Path.GetFullPath(fullPath));
                Type[] types = asm.GetTypes();
                for (int i = 0; i < types.Length; i++)
                {
                    Type type = types[i];
                    string peer = FindJavaPeerClassName(type);
                    if (peer != null)
                    {
                        mCounterpartMap[peer] = type;
                    }
                }
            }
        }

        [UnmanagedFunctionPointer(JavaVM.CC)]
        private delegate void RegisterNativeMethods_Handler(/* JNIEnv */ IntPtr environmentHandle,
            /* jobject */ IntPtr selfHandle, IntPtr /*jclass*/ declaringClassHandle);

        // Note that we MUST catch all exceptions here, since we we are being invoked from Java.  
        // We convert .NET exceptions to Java exceptions.  Within the JVM, the caller will get the Java exception.
        // Also, this code is called by Java, never by .NET.  The selfHandle parameter is part of JNI signature.
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "selfHandle")]
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        [NativeMethodInfo("registerNativeMethods", "(Ljava/lang/Class;)V", typeof(RegisterNativeMethods_Handler))]
        private static void RegisterNativeMethods(/* JNIEnv */ IntPtr environmentHandle,
            /* jobject */ IntPtr selfHandle, IntPtr /*jclass*/ declaringClassHandle)
        {
            JniEnvironment environment = JavaVM.EnvironmentFromHandle(environmentHandle);
            try
            {
                JniClass peerClass = new JniClass(environment, declaringClassHandle, JniObjectLifecycle.LocalJ2N);
                string peerClassName = peerClass.GetTypeName(environment);
                Type counterpartType;
                if (!JavaVM.JavaNativeMethodRegistrar.mCounterpartMap.TryGetValue(peerClassName, out counterpartType))
                {
                    throw new JniException("Found no counterpart for: " + peerClassName);
                }
                JavaVM.Log("Registering natives for peer: " + peerClassName + ", counterpart class: " + counterpartType.AssemblyQualifiedName);
                RegisterCounterpart(environment, peerClass, counterpartType);
            }
            catch (JniThrowableException ex)
            {
                ex.Throwable.ThrowToJavaVM(environment);
            }
            catch (Exception ex)
            {
                JniThrowable.ThrowNewToJavaVM(environment, 
                    JniClass.FindClass(environment, "java.lang.UnsatisfiedLinkError"), 
                    JniThrowable.ExtractDotNetExceptionMessage(ex));
            }
        }
    }
}
